<template>
  <div ref="slider" class="slider-warp" :class="'slider-'+direction+' '+(isMoving?'slider-moving':'')">
    <div class="slider-preload" />
    <div class="slider-container">
      <span class="slider-control" />
    </div>
  </div>
</template>

<script>
export default {
  name: 'Slider',
  props: {
    direction: {
      type: String,
      default: 'horizontal',
      validator(value) {
        return ['horizontal', 'vertical'].includes(value)
      }
    },
    preload: {
      type: Number,
      default: 0.5
    },
    progress: {
      type: Number,
      default: 0
    },
    sync: {
      type: Boolean,
      default: false
    }
  },
  data() {
    return {
      isMoving: false
    }
  },
  watch: {
    preload(val, oldVal) {
      if (val !== oldVal) {
        this.setPreload(val)
      }
    },
    progress(val, oldVal) {
      if (val !== oldVal && !this.isMoving) {
        this.setProgress(val)
      }
    }
  },
  mounted() {
    this.initSlider()
    this.setPreload(this.preload)
    this.setProgress(this.progress)
  },
  methods: {
    getRelativeElement(e, el) {
      const rect = el.getBoundingClientRect()
      return {
        x: e.clientX - rect.x,
        y: e.clientY - rect.y
      }
    },
    initSlider() {
      this.$warp = this.$refs['slider']
      this.$preload = this.$el.querySelector('.slider-preload')
      this.$container = this.$el.querySelector('.slider-container')
      this.$control = this.$el.querySelector('.slider-control')
      this.$warp.addEventListener('click', this.mouseClick)
      this.$control.addEventListener('mousedown', this.mouseDown)
    },
    mouseDown(e) {
      this.isMoving = true
      this.state = {
        x: e.clientX,
        y: e.clientY
      }
      setTimeout(() => {
        document.addEventListener('mousemove', this.mouseMove)
        document.addEventListener('mouseup', this.mouseUp)
      }, 1)
    },
    mouseMove(e) {
      const progress = this.getMovingProgress(e)
      if (this.sync) {
        this.$emit('progress-change', progress)
      }
      this.setProgress(progress)
      this.state = {
        x: e.clientX,
        y: e.clientY
      }
    },
    mouseUp(e) {
      const progress = this.getMovingProgress(e)
      this.setProgress(progress)
      this.$emit('progress-change', progress)
      this.isMoving = false
      document.removeEventListener('mousemove', this.mouseMove)
      document.removeEventListener('mouseup', this.mouseUp)
    },
    mouseClick(e) {
      const pos = this.getRelativeElement(e, this.$warp)
      const progress = this.calculateProgress(pos)
      this.$emit('progress-change', progress)
      this.setProgress(progress)
    },
    calculateProgress(pos) {
      const key = this.direction === 'horizontal' ? 'offsetWidth' : 'offsetHeight'
      const totalWidth = this.$warp[key]
      let setWidth = 0
      if (this.direction === 'horizontal') {
        setWidth = pos.x / totalWidth
      } else {
        setWidth = (totalWidth - pos.y) / totalWidth
      }
      if (setWidth < 0) {
        return 0
      } else if (setWidth >= 0 && setWidth <= 1) {
        return setWidth
      } else {
        return 1
      }
    },
    getMovingProgress(e) {
      const key = this.direction === 'vertical' ? 'offsetHeight' : 'offsetWidth'
      const dKey = this.direction === 'vertical' ? 'dy' : 'dx'
      const range = {
        dx: e.clientX - this.state.x,
        dy: e.clientY - this.state.y
      }
      const totalVal = this.$warp[key]
      const initVal = this.$container[key]
      let setVal = initVal + range[dKey]
      if (dKey === 'dy') {
        setVal = initVal - range[dKey]
      }
      const progress = setVal / totalVal
      if (progress < 0) {
        return 0
      } else if (progress >= 0 && progress <= 1) {
        return progress
      } else {
        return 1
      }
    },
    setPreload(progress) {
      const key = this.direction === 'vertical' ? 'height' : 'width'
      progress = this.minmax(progress)
      this.$preload.style[key] = (progress * 100) + '%'
    },
    setProgress(progress) {
      const key = this.direction === 'vertical' ? 'height' : 'width'
      progress = this.minmax(progress)
      this.$container.style[key] = (progress * 100) + '%'
    },
    minmax(val = 0, min = 0, max = 1) {
      if (val < min) {
        return min
      } else if (val > max) {
        return max
      } else {
        return val
      }
    }
  }
}
</script>

<style scoped lang="less">
  @control-color:#ffffff;
  @preload-color:#c8c8c8;
  @progress-color:#e63e53;
  @bg-color:#6a737d;
  .slider-warp{
    display: block;
    position: relative;
    background-color: @bg-color;
    cursor: pointer;
    border-radius: 2px;
    user-select: none;
    -webkit-tap-highlight-color:transparent;
    .slider-control{
      width:10px;
      height: 10px;
      border-radius: 5px;
      position: absolute;
      background-color: @control-color;
      opacity: 0;
      transform: scale(.5);
      box-shadow: 0 0 7px 1px @progress-color;
      transition: all .2s;
      cursor: pointer;
    }
    &:hover .slider-control,
    &.slider-moving .slider-control{
      opacity: 1;
      transform: scale(1);
    }
    .slider-container{
      position: relative;
      background-color: @progress-color;
      border-radius: 2px;
    }
    .slider-preload{
      background-color: @preload-color;
      border-radius: 2px;
      position: absolute;
    }
    &.slider-horizontal{
      width: 100%;
      height: 4px;
      .slider-preload{
        left: 0;
        top: 0;
        width: 0;
        height:100%;
      }
      .slider-container{
        position: relative;
        height: 100%;
        width: 0;
      }
      .slider-control{
        position: absolute;
        left: 100%;
        margin-left: -5px;
        top: 50%;
        margin-top: -5px;
      }
    }
    &.slider-vertical{
      height: 100%;
      width: 4px;
      .slider-container{
        position: absolute;
        bottom: 0;
        left: 0;
        width: 100%;
        height: 0;
      }
      .slider-preload{
        left: 0;
        bottom: 0;
        width: 100%;
        height: 0;
      }
      .slider-control{
        position: absolute;
        bottom: 100%;
        margin-bottom: -5px;
        left: 50%;
        margin-left: -5px;
      }
    }
  }
</style>
